package com.econ.powercloud.ems.timing.compute;

import com.econ.powercloud.clickhouse.dao.AnalogDataDAO;
import com.econ.powercloud.clickhouse.entity.AnalogData;
import com.econ.powercloud.common.base.facade.util.JacksonUtil;
import com.econ.powercloud.common.base.facade.util.TimeUtil;
import com.econ.powercloud.datahandle.facade.domain.ProcessedCalData;
import com.econ.powercloud.ems.aop.TimingRetryValid;
import com.econ.powercloud.ems.common.enums.ComputeModel;
import com.econ.powercloud.ems.common.enums.DayType;
import com.econ.powercloud.ems.common.enums.DitTypeEnum;
import com.econ.powercloud.ems.common.util.DateTimeUtil;
import com.econ.powercloud.ems.dal.entity.CalCode;
import com.econ.powercloud.ems.dal.entity.EmsComputeConfig;
import com.econ.powercloud.ems.dal.entity.EmsComputeStatistics;
import com.econ.powercloud.ems.dal.entity.EmsEnergyStation;
import com.econ.powercloud.ems.pojo.ComputeStatistics;
import com.econ.powercloud.ems.service.dao.EmsComputeConfigDAO;
import com.econ.powercloud.ems.service.dao.EmsComputeStatisticsDAO;
import com.econ.powercloud.ems.service.dao.EmsEnergyStationDAO;
import com.econ.powercloud.ems.timing.compute.strategy.ComputeCodeStrategy;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ Description:全景分析
 * 保存总数据的计算点号以及实测点号：
 * 上网电量：放电量（场景储能站节点配置的累计放电量）
 * 下网电量：并网点功率表（可能有多个）的正向有功总电能（一个实际的遥测点号Eppt）
 * 站用电量：所有设备电表（计量电表）的正向总有功电能（一个实际的遥测点号Eppt）
 * 站运行时长：场景储能站节点的运行时长
 * 等效利用系数、电站储能损耗率：储能装置计量电表的正向总有功电能（充电量）、储能装置计量电表的反向总有功电能（放电量）
 * 非计划停运系数；场景配置的停机时长点号
 * 电站实际可充放电功率：每个PCS的功率（PCS的正向有功功率）
 *
 * @ Author：hgp
 * @ Create:2022-11-25-14:02
 */
@Slf4j
@Component
public class ComputeTask {
    public static final String EON_EOFF = "eon-eoff";
    public static final String COMPUTE_STATISTIC = "compute-statistic";
    public static final String COMPUTE_STATISTIC_DAY = "compute-statistic-day";
    public static final Integer ONE_HUNDRED = 100;
    public static final Long HOUR_TIMEMILLIS = 3600000L;//每小时的毫秒数
    private final int MAX_SAVE_COUNTS = 10000;
    public static final String COLLECTION_NAME_ANALOG_PREFIX = "analog_data";
    //权重
    private static final double ACCOTPS_FACTOR = 0.2;
    private static final double ADCOTPS_FACTOR = 0.2;
    private static final double EESS_FACTOR = 0.15;
    private static final double RES_FACTOR = 0.1;
    private static final double RS_FACTOR = 0.05;
    private static final double SROPSDR_FACTOR = 0.1;
    private static final double EAF_FACTOR = 0.1;
    private static final double UOF_FACTOR = 0.05;
    private static final double AF_FACTOR = 0.05;

    @Autowired
    private EmsComputeConfigDAO emsComputeConfigDAO;
    @Autowired
    private ComputeCodeStrategy computeCodeStrategy;
    @Autowired
    private AnalogDataDAO analogDataDAO;
    @Autowired
    private EmsComputeStatisticsDAO emsComputeStatisticsDAO;
    @Autowired
    private EmsEnergyStationDAO emsEnergyStationDAO;

    /**
     * 计算所有的需要存总量的计算点号
     *
     * 上网电量：放电量（场景储能站节点配置的累计放电量）
     * 下网电量：并网点功率表（可能有多个）的正向有功总电能（一个实际的遥测点号Eppt）
     * 站用电量：所有设备电表（计量电表）的正向总有功电能（一个实际的遥测点号Eppt）
     * 站运行时长：场景储能站节点的运行时长
     * 储能装置计量电表的正向总有功电能（充电量）、储能装置计量电表的反向总有功电能（放电量）
     * 场景配置的停机时长点号
     * 每个PCS的功率（PCS的正向有功功率）
     */
    @Scheduled(cron = "0 10 0/1 * * ?")
//    @Scheduled(cron = "0 0/5 * * * ?")
    @TimingRetryValid(lockName = EON_EOFF)
    public void eonAndEoffCompute() {
        List<String> model = Lists.newArrayList(ComputeModel.EON.getModel(), ComputeModel.EOFF.getModel());
        List<EmsComputeConfig> eonAndEoff = emsComputeConfigDAO.getListByModelAndDayType(model,DayType.ALL.getCode());
        List<ProcessedCalData> processedCalDataList = Lists.newArrayList();
        eonAndEoff.stream().forEach(d -> {
            //通过策略计算出上网电量下网电量
            Double result = computeCodeStrategy.compute(d, new HashMap<>(), DateTimeUtil.getStartOfHour(new Date()));
            if(result != null){
                processedCalDataList.add(getProcessedCalData(d, result));
            }
        });
        processedCalDataList.addAll(uthAndUoh());
        processedCalDataList.addAll(stationEnergy());
        insertAnalogData2ClickHouse(processedCalDataList,System.currentTimeMillis());
    }

    /**
     * 计算站运行时长、停机时长
     * 站运行时长：储能电站运行小时数应分别统计评价周期内各储能单元的运行时间,并按照各储能单元的额定功率加权平均
     * 计算：根据PCS的状态为运行来计算运行时长，再加权计算
     * 保留维度：总、天
     */
    public List<ProcessedCalData> uthAndUoh() {
        List<String> model = Lists.newArrayList(ComputeModel.UTH.getModel(), ComputeModel.PI.getModel()
                , ComputeModel.PN.getModel(), ComputeModel.UOH.getModel()
                , ComputeModel.UOHI.getModel(), ComputeModel.UOHN.getModel());
        List<EmsComputeConfig> computeConfigList = emsComputeConfigDAO.getListByModelAndDayType(model,DayType.ALL.getCode());
        if (CollectionUtils.isEmpty(computeConfigList)) {
            return Lists.newArrayList();
        }
        Map<String, List<EmsComputeConfig>> configMap = computeConfigList.stream().collect(Collectors.groupingBy(EmsComputeConfig::getComputeModel));
        List<EmsComputeConfig> piUohiList = Lists.newArrayList();
        piUohiList.addAll(configMap.get(ComputeModel.PI.getModel()));
        piUohiList.addAll(configMap.get(ComputeModel.UOHI.getModel()));
        //第一个map的key为stationID，第二个map的key为pid
        Map<String, Map<String, Double>> piUohiMap = new HashMap<>();
        piUohiList.stream().forEach(d -> {
            //计算储能单元额定功率*运行时长、储能单元额定功率*停机时长
            Double result = computeCodeStrategy.compute(d, new HashMap<>(), null);
            if(result != null){
                Map<String, Double> doubleMap;
                if (piUohiMap.containsKey(d.getEnergyStationId())) {
                    doubleMap = piUohiMap.get(d.getEnergyStationId());
                } else {
                    doubleMap = new HashMap<>();
                }
                doubleMap.put(d.getComputeCode(), result);
                piUohiMap.put(d.getEnergyStationId(), doubleMap);
            }
        });
        List<EmsComputeConfig> pnUohnList = Lists.newArrayList();
        pnUohnList.addAll(configMap.get(ComputeModel.PN.getModel()));
        pnUohnList.addAll(configMap.get(ComputeModel.UOHN.getModel()));
        //第一个map的key为stationID，第二个map的key为pid,这里的第二个map有且最多只有一个值
        Map<String, Map<String, Double>> pnUohnMap = new HashMap<>();
        pnUohnList.stream().forEach(pn -> {
            //计算累计储能单元额定功率*运行时长、累计储能单元额定功率*停机时长
            Map<String, Double> calMap = MapUtils.isEmpty(piUohiMap.get(pn.getEnergyStationId()))?new HashMap<>():piUohiMap.get(pn.getEnergyStationId());
            Double pnResult = computeCodeStrategy.compute(pn, calMap, null);
            if(pnResult != null){
                Map<String, Double> pnResultMap ;
                if (pnUohnMap.containsKey(pn.getEnergyStationId())){
                    pnResultMap = pnUohnMap.get(pn.getEnergyStationId());
                }else {
                    pnResultMap = new HashMap<>();
                }
                pnResultMap.put(pn.getComputeCode(), pnResult);
                pnUohnMap.put(pn.getEnergyStationId(), pnResultMap);
            }
        });

        List<ProcessedCalData> processedCalDataList = Lists.newArrayList();
        ArrayList<EmsComputeConfig> uthAndUohList = Lists.newArrayList();
        uthAndUohList.addAll(configMap.get(ComputeModel.UTH.getModel()));
        uthAndUohList.addAll(configMap.get(ComputeModel.UOH.getModel()));
        uthAndUohList.stream().forEach(uth -> {
            //站运行时长、停机时长
            Map<String, Double> calMap = MapUtils.isEmpty(pnUohnMap.get(uth.getEnergyStationId()))?new HashMap<>():pnUohnMap.get(uth.getEnergyStationId());
            Double uthResult = computeCodeStrategy.compute(uth, calMap, null);
            if(uthResult != null){
                processedCalDataList.add(getProcessedCalData(uth, uthResult));
            }
        });
        //计算好的数据发往kafka
        return processedCalDataList;
    }

    /**
     * 站用电量
     * 站用电量：站用电量应为评价周期内维持储能电站运行的监控系统、照明动力及暖通空调等所耗的电量总和，可通过监控系统从站用电回路中的计量表计取。
     * 计算：在有辅助系统的额外电表情况下，计算正向总有功，按照小时来计算，保存日数据，后续做报表
     * 保留维度：总、天
     */
    public List<ProcessedCalData> stationEnergy() {
        List<String> model = Lists.newArrayList(ComputeModel.SPC.getModel());
        List<EmsComputeConfig> computeConfigList = emsComputeConfigDAO.getListByModelAndDayType(model,DayType.ALL.getCode());
        List<ProcessedCalData> processedCalDataList = Lists.newArrayList();
        computeConfigList.stream().forEach(station -> {
            //站用电量
            Double result = computeCodeStrategy.compute(station, new HashMap<>(), null);
            if (result!=null){
                processedCalDataList.add(getProcessedCalData(station, result));
            }
        });
        //计算好的数据发往kafka
        return processedCalDataList;
    }

    @Scheduled(cron = "0 30 0 * * ?")
//    @Scheduled(cron = "0 0/6 * * * ?")
    @TimingRetryValid(lockName = COMPUTE_STATISTIC_DAY)
    public void computeStatisticDay() {
        //获取所有的参与计算的点号的历史数据
        List<EmsComputeConfig> allList = emsComputeConfigDAO.getListByCondition(null,
                null, null, DayType.ONE_DAY.getCode(), null, null);
        Map<String, List<EmsComputeConfig>> configMap = allList.stream().collect(Collectors.groupingBy(EmsComputeConfig::getEnergyStationId));
        List<AnalogData> ycData = Lists.newArrayList();
        Map<String, EmsEnergyStation> stationMap = emsEnergyStationDAO.list().stream()
                .collect(Collectors.toMap(EmsEnergyStation::getEnergyStationId, d -> d, (d1, d2) -> d2));
        //当天零点时间戳
        Long endTime = getCurrentDayZeroTime();
//        Long endTime = System.currentTimeMillis();
        //昨天零点时间戳
        Long yesterdayTime = endTime - DayType.ONE_DAY.getHour() * HOUR_TIMEMILLIS;
        configMap.forEach((station,configList)->{
            //前一天天前零点时间戳
            EmsEnergyStation emsEnergyStation = stationMap.get(station);
            if (emsEnergyStation == null) {
                return;
            }
            if (endTime < emsEnergyStation.getCommissioningDate()) {
                return;
            }
            Long startTime = yesterdayTime;
            //判断数据查询开始时间是否小于储能站投运时间
            if (startTime < emsEnergyStation.getCommissioningDate()) {
                startTime = emsEnergyStation.getCommissioningDate();
            }
            List<CalCode> allCalCodeList = Lists.newArrayList();
            configList.stream().forEach(config -> allCalCodeList.addAll(config.getCalCode()));
            List<CalCode> ycCalCode = allCalCodeList.stream().filter(calCode ->
                    DitTypeEnum.isYcData(calCode.getDitType())).collect(Collectors.toList());
            ycData.addAll(analogDataDAO.selectByPidsAndTime(ycCalCode.stream().map(CalCode::getPid)
                    .collect(Collectors.toList()), true, startTime, endTime));
        });
        Map<String, List<AnalogData>> ycPidMap = ycData.stream().collect(Collectors.groupingBy(AnalogData::getPid));
        //key--pid  ,value--pid在这段时间内的差值
        Map<String, Double> pidValueMap = new HashMap<>();
        ycPidMap.forEach((k, v) -> {
            if (CollectionUtils.isEmpty(v)) {
                return;
            }
            List<AnalogData> list = v.stream().sorted(Comparator.comparing(AnalogData::getT)).collect(Collectors.toList());
            AnalogData startValue = list.get(0);
            AnalogData lastValue = list.get(list.size() - 1);
            Double value = Double.parseDouble(lastValue.getV()) - Double.parseDouble(startValue.getV());
            pidValueMap.put(startValue.getPid(), new BigDecimal(value).setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue());
        });
        List<ProcessedCalData> processedCalDataList = Lists.newArrayList();

        List<ComputeStatistics> allStatis = Lists.newArrayList();
        //计算站用电量、上网电量、下网电量、电站运行时长
        allStatis.addAll(spcEonEoffUth(allList, pidValueMap));
        //计算电站等效利用系数
        allStatis.addAll(eaf(allList, pidValueMap));
        //计算 电站综合效率
        allStatis.addAll(eess(allList, pidValueMap));
        //计算 站用电率
        allStatis.addAll(rs(allList, pidValueMap));
        //计算 非计划停运系数、电站利用系数
        allStatis.addAll(uofAndUtf(allList, pidValueMap));
        //计算 电站储能损耗率
        allStatis.addAll(res(allList,pidValueMap));
        //计算充放电能转换效率
        allStatis.addAll(esu(allList,pidValueMap));
        //电站实际可充放电功率
        allStatis.addAll(accotps(allList,pidValueMap));

        allStatis.stream().forEach(statistics->{
            ProcessedCalData processedCalData = ProcessedCalData.builder().dqf(19)
                    .pid(statistics.getComputeCode())
                    .type(statistics.getMeasureType())
                    .unit(statistics.getUnit())
                    .remark(statistics.getComputeName())
                    .deviceCode(statistics.getDeviceCode())
                    .dataTime(yesterdayTime).processedTime(yesterdayTime).time(yesterdayTime)
                    .val(statistics.getValue().toString())
                    .build();
            processedCalDataList.add(processedCalData);
        });

        //计算运行评价总分
        Map<String, List<ComputeStatistics>> map = allStatis.stream().collect(Collectors.groupingBy(ComputeStatistics::getEnergyStationId));
        Map<String, EmsComputeConfig> adcMap = allList.stream().filter(d -> ComputeModel.ADCOTPS.getModel().equals(d.getComputeModel()))
                .collect(Collectors.toMap(EmsComputeConfig::getEnergyStationId, d -> d, (d1, d2) -> d1 = d2));
        Map<String, Double> accMap = allList.stream().filter(d -> ComputeModel.ACCOTPS.getModel().equals(d.getComputeModel()))
                .collect(Collectors.toMap(EmsComputeConfig::getEnergyStationId, EmsComputeConfig::getConstant));
        List<EmsComputeConfig> sList = allList.stream().filter(d ->
                ComputeModel.S.getModel().equals(d.getComputeModel())).collect(Collectors.toList());
        sList.stream().forEach(s->{
            List<ComputeStatistics> computeStatistics = map.get(s.getEnergyStationId());
            Map<String, Double> modelMap = computeStatistics.stream().collect(Collectors.
                    toMap(ComputeStatistics::getComputeModel, ComputeStatistics::getValue, (d1, d2) -> d1 = d2));
            modelMap.put(adcMap.get(s.getEnergyStationId()).getComputeModel(),adcMap.get(s.getEnergyStationId()).getConstant());
            Double score = s(modelMap, accMap.get(s.getEnergyStationId()), adcMap.get(s.getEnergyStationId()).getConstant());
            processedCalDataList.add(getProcessedCalData(s,score));
        });

        //计算好的数据存ClickHouse
        insertAnalogData2ClickHouse(processedCalDataList,yesterdayTime);
    }

    /**
     * 电站实际可充放电功率
     * 注意：ACCOTPS配置时，cal_code字段配置PCS的总功率,constant字段配置储能站的总额定功率
     * @param allList
     * @param PidValueMap
     * @return
     */
    private List<ComputeStatistics> accotps(List<EmsComputeConfig> allList, Map<String, Double> PidValueMap){
        List<ComputeStatistics> result = Lists.newArrayList();
        List<EmsComputeConfig> accList = allList.stream().filter(d ->
                ComputeModel.ACCOTPS.getModel().equals(d.getComputeModel())).collect(Collectors.toList());
        accList.stream().forEach(acc -> {
            Double sum = 0.0;
            List<CalCode> accCalCodeList = acc.getCalCode();
            for (CalCode calCode:accCalCodeList){
                if (PidValueMap.containsKey(calCode.getPid())){
                    sum += PidValueMap.get(calCode.getPid());
                }
            }
            Integer decimalNumber = acc.getDecimalNumber() == null ? 2 : acc.getDecimalNumber();
            ComputeStatistics build = ComputeStatistics.builder().computeCode(acc.getComputeCode())
                    .deviceCode(acc.getDeviceCode()).computeModel(acc.getComputeModel())
                    .computeName(acc.getComputeName()).dayType(acc.getDayType())
                    .unit(acc.getUnit()).energyStationId(acc.getEnergyStationId())
                    .value(new BigDecimal(sum).setScale(decimalNumber,BigDecimal.ROUND_HALF_UP).doubleValue())
                    .measureType(acc.getMeasureType()).build();
            result.add(build);
        });
        return result;
    }

    /**
     * 全景分析统计数据
     */
    @Scheduled(cron = "0 30 0 * * ?")
//    @Scheduled(cron = "0 0/6 * * * ?")
    @TimingRetryValid(lockName = COMPUTE_STATISTIC)
    public void computeStatistic() {
        //获取所有的参与计算的点号的历史数据
        List<EmsComputeConfig> allList = emsComputeConfigDAO.getListByCondition(null,
                null, null, DayType.MONTH.getCode(), null, null);
        //当天零点时间戳
        Long endTime = getCurrentDayZeroTime();
//        Long endTime = System.currentTimeMillis();
        //30天前零点时间戳
        Long timeBefore30 = endTime - DayType.MONTH.getHour() * HOUR_TIMEMILLIS;
        List<AnalogData> ycData = Lists.newArrayList();
        Map<String, EmsEnergyStation> stationMap = emsEnergyStationDAO.list().stream()
                .collect(Collectors.toMap(EmsEnergyStation::getEnergyStationId, d -> d, (d1, d2) -> d2));
        Map<String, List<EmsComputeConfig>> configMap = allList.stream().collect(Collectors.groupingBy(EmsComputeConfig::getEnergyStationId));
        configMap.forEach((station, configList) -> {
            EmsEnergyStation emsEnergyStation = stationMap.get(station);
            if (emsEnergyStation == null) {
                return;
            }
            if (endTime < emsEnergyStation.getCommissioningDate()) {
                return;
            }
            Long startTime = timeBefore30;
            //判断数据查询开始时间是否小于储能站投运时间
            if (startTime < emsEnergyStation.getCommissioningDate()) {
                startTime = emsEnergyStation.getCommissioningDate();
            }
            List<CalCode> allCalCodeList = Lists.newArrayList();
            configList.stream().forEach(config -> allCalCodeList.addAll(config.getCalCode()));
            List<CalCode> ycCalCode = allCalCodeList.stream().filter(calCode ->
                    DitTypeEnum.isYcData(calCode.getDitType())).collect(Collectors.toList());
            ycData.addAll(analogDataDAO.selectByPidsAndTime(ycCalCode.stream().map(CalCode::getPid)
                    .collect(Collectors.toList()), true, startTime, endTime));
        });
        Map<String, List<AnalogData>> ycPidMap = ycData.stream().collect(Collectors.groupingBy(AnalogData::getPid));

        //key--pid  ,value--pid在这段时间内的差值
        Map<String, Double> pidValueMap = new HashMap<>();
        ycPidMap.forEach((k, v) -> {
            if (CollectionUtils.isEmpty(v)) {
                return;
            }
            List<AnalogData> list = v.stream().sorted(Comparator.comparing(AnalogData::getT)).collect(Collectors.toList());
            AnalogData startValue = list.get(0);
            AnalogData lastValue = list.get(list.size() - 1);
            Double value = Double.parseDouble(lastValue.getV()) - Double.parseDouble(startValue.getV());
            pidValueMap.put(startValue.getPid(), new BigDecimal(value).setScale(2,BigDecimal.ROUND_HALF_UP).doubleValue());
        });

        List<ComputeStatistics> allStatis = Lists.newArrayList();
        //计算站用电量、上网电量、下网电量、电站运行时长
        allStatis.addAll(spcEonEoffUth(allList, pidValueMap));
        //计算电站等效利用系数
        allStatis.addAll(eaf(allList, pidValueMap));
        //计算 电站综合效率
        allStatis.addAll(eess(allList, pidValueMap));
        //计算 站用电率
        allStatis.addAll(rs(allList, pidValueMap));
        //计算 非计划停运系数、电站利用系数
        allStatis.addAll(uofAndUtf(allList, pidValueMap));
        //计算 电站储能损耗率
        allStatis.addAll(res(allList,pidValueMap));
        //计算充放电能转换效率
        allStatis.addAll(esu(allList,pidValueMap));

        List<EmsComputeStatistics> emsComputeStatisticsList = JacksonUtil.convertToList(allStatis, EmsComputeStatistics.class);
        //将计算好的数据保存到ems_compute_statistics表中
        List<EmsComputeStatistics> statisticsList = emsComputeStatisticsDAO.list();
        Map<String, EmsComputeStatistics> statisticsMap = statisticsList.stream().
                collect(Collectors.toMap(d -> getMapKey(d.getEnergyStationId(), d.getComputeCode()), d -> d));
        List<EmsComputeStatistics> newStatistics = Lists.newArrayList();
        List<EmsComputeStatistics> updateStatistics = Lists.newArrayList();
        emsComputeStatisticsList.stream().forEach(statistics->{
            String mapKey = getMapKey(statistics.getEnergyStationId(), statistics.getComputeCode());
            statistics.setGmtModify(System.currentTimeMillis());
            if (statisticsMap.containsKey(mapKey)){
                EmsComputeStatistics emsComputeStatistics = statisticsMap.get(mapKey);
                statistics.setId(emsComputeStatistics.getId());
                updateStatistics.add(statistics);
            }else {
                statistics.setGmtCreate(System.currentTimeMillis());
                newStatistics.add(statistics);
            }
        });
        emsComputeStatisticsDAO.updateBatchById(updateStatistics);
        emsComputeStatisticsDAO.saveBatch(newStatistics);
    }


    /**
     * 计算 非计划停运系数 : (非计划停运小时数(停机时间)/统计小时数) * 100%
     * 计算 电站利用系数 ：（电站运行小时数/统计小时数） * 100%
     * 注意：配置UOF时，cal_code配置一个点号，这个点号为场景配置的停机时长
     *      配置UTF时，cal_code配置一个点号，这个点号为总的站运行时长
     * @param allList
     * @param pidValueMap
     * @return
     */
    private List<ComputeStatistics> uofAndUtf(List<EmsComputeConfig> allList, Map<String, Double> pidValueMap) {
        List<ComputeStatistics> result = Lists.newArrayList();
        List<EmsComputeConfig> uofList = allList.stream().filter(d ->
                ComputeModel.UOF.getModel().equals(d.getComputeModel())
                        || ComputeModel.UTF.getModel().equals(d.getComputeModel()))
                .collect(Collectors.toList());
        uofList.stream().forEach(uof -> {
            CalCode calCode = uof.getCalCode().get(0);
            Double value = 0.0;
            if (pidValueMap.containsKey(calCode.getPid())) {
                value = (pidValueMap.get(calCode.getPid()) / DayType.getHourFromCode(uof.getDayType())) * ONE_HUNDRED;
            }
            ComputeStatistics build = ComputeStatistics.builder().computeCode(uof.getComputeCode())
                    .deviceCode(uof.getDeviceCode()).computeModel(uof.getComputeModel())
                    .computeName(uof.getComputeName()).dayType(uof.getDayType())
                    .unit(uof.getUnit()).energyStationId(uof.getEnergyStationId())
                    .value(new BigDecimal(value).setScale(uof.getDecimalNumber(), BigDecimal.ROUND_HALF_UP).doubleValue())
                    .measureType(uof.getMeasureType()).build();
            result.add(build);
        });
        return result;
    }

    /**
     * 计算 充放电能转换效率：（总充电量/总放电量） * 100%
     * 注意：配置ESU时，cal_code配置两个点号，第一个点号配置总的放电量（ED），第二个配置总的充电量（EC）
     * @param allList
     * @param pidValueMap
     * @return
     */
    private List<ComputeStatistics> esu(List<EmsComputeConfig> allList, Map<String, Double> pidValueMap) {
        List<ComputeStatistics> result = Lists.newArrayList();
        List<EmsComputeConfig> esuList = allList.stream().filter(d ->
                ComputeModel.ESU.getModel().equals(d.getComputeModel())).collect(Collectors.toList());
        esuList.stream().forEach(esu -> {
            List<CalCode> esuCalCodeList = esu.getCalCode();
            Double ed = pidValueMap.get(esuCalCodeList.get(0).getPid());
            Double ec = pidValueMap.get(esuCalCodeList.get(1).getPid());
            BigDecimal bigDecimal = null;
            Integer devimalNumber = esu.getDecimalNumber() == null ? 2 : esu.getDecimalNumber();
            if (ed != null && ec != null) {
                if (ec==0){
                    bigDecimal = new BigDecimal(0);
                }else {
                    bigDecimal = new BigDecimal((ed / ec) * ONE_HUNDRED).setScale(devimalNumber, BigDecimal.ROUND_HALF_UP);
                }
            }
            ComputeStatistics build = ComputeStatistics.builder().computeCode(esu.getComputeCode())
                    .deviceCode(esu.getDeviceCode()).computeModel(esu.getComputeModel())
                    .computeName(esu.getComputeName()).dayType(esu.getDayType())
                    .unit(esu.getUnit()).energyStationId(esu.getEnergyStationId())
                    .value(bigDecimal == null ? 0.0 : bigDecimal.doubleValue())
                    .measureType(esu.getMeasureType()).build();
            result.add(build);
        });
        return result;
    }

    /**
     * 计算 站用电率 ：站用电量除以下网电量再乘100%
     * 注意：配置RS时，cal_code配置两个点号，第一个点号为总的站用电量，第二个点号为总的下网电量
     * @param allList
     * @param pidValueMap
     * @return
     */
    private List<ComputeStatistics> rs(List<EmsComputeConfig> allList, Map<String, Double> pidValueMap) {
        List<ComputeStatistics> result = Lists.newArrayList();
        List<EmsComputeConfig> rsList = allList.stream()
                .filter(d -> ComputeModel.RS.getModel().equals(d.getComputeModel())).collect(Collectors.toList());
        rsList.stream().forEach(rs -> {
            List<CalCode> rsCalCodeList = rs.getCalCode();
            CalCode spc = rsCalCodeList.get(0);
            CalCode eoff = rsCalCodeList.get(1);
            Integer devimalNumber = rs.getDecimalNumber() == null ? 2 : rs.getDecimalNumber();
            BigDecimal bigDecimal = new BigDecimal(0);
            //计算站用电率：站用电量除以下网电量再乘100%
            if (pidValueMap.containsKey(spc.getPid()) && pidValueMap.containsKey(eoff.getPid())) {
                Double spcValue = pidValueMap.get(spc.getPid());
                Double eoffValue = pidValueMap.get(eoff.getPid());
                if (spcValue != 0 && eoffValue != 0) {
                    bigDecimal = new BigDecimal((spcValue / eoffValue) * ONE_HUNDRED)
                            .setScale(devimalNumber, BigDecimal.ROUND_HALF_UP);
                }
            }
            ComputeStatistics build = ComputeStatistics.builder().computeCode(rs.getComputeCode())
                    .deviceCode(rs.getDeviceCode()).computeModel(rs.getComputeModel())
                    .computeName(rs.getComputeName()).dayType(rs.getDayType())
                    .unit(rs.getUnit()).energyStationId(rs.getEnergyStationId())
                    .measureType(rs.getMeasureType()).value(bigDecimal.doubleValue()).build();
            result.add(build);
        });
        return result;
    }

    /**
     * 计算电站储能损耗率：（（充电量总和 - 放电量总和）/上网电量 ）* 100%
     * 注意：配置RES时，cal_code配置三个点号，第一个点号为各储能单元充电量总和，第二个点号为各储能单元放电量总和，
     *       第三个点号为总的上网电量的computeCode
     * @param allList
     * @param pidValueMap
     * @return
     */
    private List<ComputeStatistics> res(List<EmsComputeConfig> allList, Map<String, Double> pidValueMap) {
        List<ComputeStatistics> result = Lists.newArrayList();
        List<EmsComputeConfig> resList = allList.stream()
                .filter(d -> ComputeModel.RES.getModel().equals(d.getComputeModel())).collect(Collectors.toList());
        resList.stream().forEach(res->{
            List<CalCode> calCodeList = res.getCalCode();
            CalCode ec = calCodeList.get(0);
            CalCode ed = calCodeList.get(1);
            CalCode eon = calCodeList.get(2);
            Double ecValue = 0.0;
            Double edValue = 0.0;
            if (pidValueMap.containsKey(ec.getPid())) {
                ecValue = pidValueMap.get(ec.getPid());
            }
            if (pidValueMap.containsKey(ed.getPid())) {
                edValue = pidValueMap.get(ed.getPid());
            }
            Double eonValue = Double.MAX_VALUE;
            if (pidValueMap.containsKey(eon.getPid())){
                eonValue = pidValueMap.get(eon.getPid());
            }
            Integer decimalNumber = res.getDecimalNumber() == null ? 2 : res.getDecimalNumber();
            if (eonValue==0){
                eonValue = Double.MAX_VALUE;
            }
            Double value = ((ecValue - edValue) / eonValue) * ONE_HUNDRED;
            BigDecimal bigDecimal = new BigDecimal(value)
                    .setScale(decimalNumber, BigDecimal.ROUND_HALF_UP);
            ComputeStatistics build = ComputeStatistics.builder().computeCode(res.getComputeCode())
                    .deviceCode(res.getDeviceCode()).computeModel(res.getComputeModel())
                    .computeName(res.getComputeName()).dayType(res.getDayType())
                    .unit(res.getUnit()).energyStationId(res.getEnergyStationId())
                    .measureType(res.getMeasureType()).value(bigDecimal.doubleValue()).build();
            result.add(build);
        });
        return result;
    }

    /**
     * 计算 电站综合效率 ：（上网电量 / 下网电量）* 100%
     * 注意：配置EESS时，cal_code字段配置第一个是总的上网电量的computeCode，第二个是总的下网电量的computeCode
     * @param allList
     * @param pidValueMap
     * @return
     */
    private List<ComputeStatistics> eess(List<EmsComputeConfig> allList, Map<String, Double> pidValueMap) {
        //eess配置时calCode 配置两个点号，第一个点号为上网电量（总），第二个点号为下网电量（总）
        List<ComputeStatistics> result = Lists.newArrayList();
        List<EmsComputeConfig> eessList = allList.stream().filter(d ->
                ComputeModel.EESS.getModel().equals(d.getComputeModel())).collect(Collectors.toList());
        eessList.stream().forEach(eess -> {
            List<CalCode> calCodeList = eess.getCalCode();
            CalCode eon = calCodeList.get(0);
            CalCode eoff = calCodeList.get(1);
            Double eonValue = 0.0;
            Double eoffValue = 0.0;
            if (pidValueMap.containsKey(eon.getPid())) {
                eonValue = pidValueMap.get(eon.getPid());
            }
            if (pidValueMap.containsKey(eoff.getPid())) {
                eoffValue = pidValueMap.get(eoff.getPid());
            }
            Double resultValue = eoffValue == 0 ? 0.0 : (eonValue / eoffValue) * ONE_HUNDRED;
            Integer decimalNumber = eess.getDecimalNumber() == null ? 2 : eess.getDecimalNumber();
            BigDecimal bigDecimal = new BigDecimal(resultValue).setScale(decimalNumber, BigDecimal.ROUND_HALF_UP);
            ComputeStatistics build = ComputeStatistics.builder().computeCode(eess.getComputeCode())
                    .deviceCode(eess.getDeviceCode()).computeModel(eess.getComputeModel())
                    .computeName(eess.getComputeName()).dayType(eess.getDayType())
                    .unit(eess.getUnit()).energyStationId(eess.getEnergyStationId())
                    .measureType(eess.getMeasureType()).value(bigDecimal.doubleValue()).build();
            result.add(build);
        });
        return result;
    }

    /**
     * 电站等效利用系数 ：（n*（储能单元额定功率 * 储能单元等效利用系数））/储能站额定功率
     * 储能单元等效利用系数：(（储能单元充电量+储能单元放电量）/(储能单元额定功率*统计小时数)) * 100%
     * 注意：配置EAFI时cal_code字段需要配置两个，第一个为当前储能单元的总充电量，第二个为当前储能单元的总放电量
     *          ，constant字段配置储能单元的额定功率
     *      配置EAF时，cal_code字段根据搭配的EAFI配置个数，pid为EAFI配置的computeCode，constant字段配置储能站的额定功率
     * @param allList
     * @param pidValueMap
     * @return
     */
    private List<ComputeStatistics> eaf(List<EmsComputeConfig> allList, Map<String, Double> pidValueMap) {
        List<ComputeStatistics> result = Lists.newArrayList();
        //储能单元等效利用系数
        List<EmsComputeConfig> eafiList = allList.stream()
                .filter(d -> ComputeModel.EAFI.getModel().equals(d.getComputeModel())).collect(Collectors.toList());
        //储能站等效利用系数
        List<EmsComputeConfig> eafList = allList.stream()
                .filter(d -> ComputeModel.EAF.getModel().equals(d.getComputeModel())).collect(Collectors.toList());
        Map<String, Double> eafiMap = new HashMap<>();
        //计算EAFI
        eafiList.stream().forEach(eafi -> {
            List<CalCode> calCodeList = eafi.getCalCode();
            Double ecidi = 0.0;
            for (CalCode calCode : calCodeList) {
                if (pidValueMap.containsKey(calCode.getPid())) {
                    ecidi += pidValueMap.get(calCode.getPid());
                }
            }
            Double resultValue ;
            if (eafi.getConstant()==null||eafi.getConstant()==0){
                resultValue = 0.0;
            }else {
                Double piph = DayType.getHourFromCode(eafi.getDayType()) * eafi.getConstant();
                resultValue = ((ecidi / piph) * ONE_HUNDRED) * eafi.getConstant();
            }
            eafiMap.put(eafi.getComputeCode(), resultValue);
        });
        //计算EAF
        eafList.stream().forEach(eaf -> {
            List<CalCode> calCodeList = eaf.getCalCode();
            Double sum = 0.0;
            for (CalCode calCode : calCodeList) {
                if (eafiMap.containsKey(calCode.getPid())) {
                    sum += eafiMap.get(calCode.getPid());
                }
            }
            Double resultValue = (eaf.getConstant() == null || eaf.getConstant() == 0) ? 0.0 : sum / eaf.getConstant();
            Integer decimalNumber = eaf.getDecimalNumber() == null ? 2 : eaf.getDecimalNumber();
            BigDecimal value = new BigDecimal(resultValue).setScale(decimalNumber, BigDecimal.ROUND_HALF_UP);
            ComputeStatistics build = ComputeStatistics.builder().computeCode(eaf.getComputeCode())
                    .deviceCode(eaf.getDeviceCode()).computeModel(eaf.getComputeModel())
                    .computeName(eaf.getComputeName()).dayType(eaf.getDayType())
                    .unit(eaf.getUnit()).energyStationId(eaf.getEnergyStationId())
                    .measureType(eaf.getMeasureType()).value(value.doubleValue()).build();
            result.add(build);
        });
        return result;
    }

    /**
     * 计算站用电量、上网电量、下网电量、站运行时长、非计划停运系数
     * 注意：配置这些点号时，cal_code 字段配置其当前computeModel对应的总的计算点号
     * @param allList
     */
    private List<ComputeStatistics> spcEonEoffUth(List<EmsComputeConfig> allList, Map<String, Double> PidValueMap) {
        List<ComputeStatistics> result = Lists.newArrayList();
        List<EmsComputeConfig> spcEonEoffUthList = allList.stream()
                .filter(d -> ComputeModel.SPC.getModel().equals(d.getComputeModel())
                        || ComputeModel.EON.getModel().equals(d.getComputeModel())
                        || ComputeModel.EOFF.getModel().equals(d.getComputeModel())
                        || ComputeModel.UOH.getModel().equals(d.getComputeModel())
                        || ComputeModel.UTH.getModel().equals(d.getComputeModel())).collect(Collectors.toList());

        spcEonEoffUthList.stream().forEach(spcEonEoffUth -> {
            Double value = 0.0;
            CalCode calCode = spcEonEoffUth.getCalCode().get(0);
            if (PidValueMap.containsKey(calCode.getPid())){
                value = PidValueMap.get(calCode.getPid());
            }
            Integer decimalNumber = spcEonEoffUth.getDecimalNumber() == null ? 2 : spcEonEoffUth.getDecimalNumber();
            ComputeStatistics build = ComputeStatistics.builder().computeCode(spcEonEoffUth.getComputeCode())
                    .deviceCode(spcEonEoffUth.getDeviceCode()).computeModel(spcEonEoffUth.getComputeModel())
                    .computeName(spcEonEoffUth.getComputeName()).dayType(spcEonEoffUth.getDayType())
                    .unit(spcEonEoffUth.getUnit()).energyStationId(spcEonEoffUth.getEnergyStationId())
                    .value(new BigDecimal(value).setScale(decimalNumber,BigDecimal.ROUND_HALF_UP).doubleValue())
                    .measureType(spcEonEoffUth.getMeasureType()).build();
            result.add(build);
        });
        return result;
    }

    /**
     * 计算运行评价总分
     * @param valueMap 计算所需要用到的指标值。 key: computeModel value: 值
     * @param ratedPower 电站额定功率
     * @param ratedEnergy 电站额定能量
     */
    private Double s(Map<String, Double> valueMap, double ratedPower, double ratedEnergy) {
        double resultScore = 0;
        for(Map.Entry<String, Double> entry : valueMap.entrySet()) {
            String key = entry.getKey();
            Double value = entry.getValue();
            double score = 0;
            if(StringUtils.equals(key, ComputeModel.ACCOTPS.getModel())) {
                double ratio = value/ratedPower;
                if (ratio >= 1) {
                    score = 100;
                } else if (ratio >=0.9 && ratio <1) {
                    score = 90;
                } else if (ratio >=0.8 && ratio <0.9) {
                    score = 80;
                } else if (ratio >=0.7 && ratio <0.8) {
                    score = 70;
                } else if (ratio >=0.6 && ratio <0.7) {
                    score = 60;
                } else {
                    score = 50;
                }
                resultScore += ACCOTPS_FACTOR * score;
            } else if (StringUtils.equals(key, ComputeModel.ADCOTPS.getModel())) {
                double ratio = value/ratedEnergy;
                if (ratio == 1) {
                    score = 100;
                } else if (ratio >=0.9 && ratio <1) {
                    score = 90;
                } else if (ratio >=0.8 && ratio <0.9) {
                    score = 80;
                } else if (ratio >=0.7 && ratio <0.8) {
                    score = 70;
                } else if (ratio >=0.6 && ratio <0.7) {
                    score = 60;
                } else {
                    score = 50;
                }
                resultScore += ADCOTPS_FACTOR * score;
            } else if (StringUtils.equals(key, ComputeModel.EESS.getModel())) {
                if (value >= 90) {
                    score = 100;
                } else if (value >= 80 && value < 90) {
                    score = 90;
                } else if (value >= 70 && value < 80) {
                    score = 80;
                } else if (value >= 60 && value < 70) {
                    score = 70;
                } else {
                    score = 60;
                }
                resultScore += EESS_FACTOR * score;
            } else if (StringUtils.equals(key, ComputeModel.RES.getModel())) {
                if (value <= 10) {
                    score = 100;
                } else if (value > 10 && value <= 20) {
                    score = 95;
                } else if (value > 20 && value <= 30) {
                    score = 90;
                } else if (value > 30 && value <= 40) {
                    score = 85;
                } else {
                    score = 80;
                }
                resultScore += RES_FACTOR * score;
            } else if (StringUtils.equals(key, ComputeModel.RS.getModel())) {
                if (value <= 5) {
                    score = 100;
                } else if (value > 5 && value <= 10) {
                    score = 90;
                } else if (value > 10 && value <= 15) {
                    score = 80;
                } else if (value > 15 && value <= 20) {
                    score = 70;
                } else {
                    score = 60;
                }
                resultScore += RS_FACTOR * score;
            } else if (StringUtils.equals(key, ComputeModel.SROPSDR.getModel())) {
                if (value >= 99) {
                    score = 100;
                } else if (value >= 98 && value < 99) {
                    score = 90;
                } else if (value >= 97 && value < 98) {
                    score = 80;
                } else if (value >= 95 && value < 97) {
                    score = 70;
                } else {
                    score = 60;
                }
                resultScore += SROPSDR_FACTOR * score;
            } else if (StringUtils.equals(key, ComputeModel.EAF.getModel())) {
                if (value >= 95) {
                    score = 100;
                } else if (value >= 90 && value < 95) {
                    score = 90;
                } else if (value >= 85 && value < 90) {
                    score = 80;
                } else if (value >= 80 && value < 85) {
                    score = 70;
                } else {
                    score = 60;
                }
                resultScore += EAF_FACTOR * score;
            } else if (StringUtils.equals(key, ComputeModel.UOF.getModel())) {
                if (value == 0) {
                    score = 100;
                } else if (value > 0 && value <= 5) {
                    score = 90;
                } else if (value > 5 && value <= 10) {
                    score = 80;
                } else if (value > 10 && value <= 15) {
                    score = 70;
                } else {
                    score = 60;
                }
                resultScore += UOF_FACTOR * score;
            } else if (StringUtils.equals(key, ComputeModel.AF.getModel())) {
                if (value == 100) {
                    score = 100;
                } else if (value >= 95 && value < 100) {
                    score = 90;
                } else if (value >= 90 && value < 95) {
                    score = 80;
                } else if (value >= 85 && value < 90) {
                    score = 70;
                } else {
                    score = 60;
                }
                resultScore += AF_FACTOR * score;
            }
        }
        return resultScore;
    }

    /**
     * 获取当天零点时间戳
     * @return
     */
    public Long getCurrentDayZeroTime() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        return calendar.getTimeInMillis();
    }

    public String getMapKey(String str1, String str2) {
        return str1 + "" + str2;
    }

    /**
     * 组装ProcessedCalData对象
     */
    private ProcessedCalData getProcessedCalData(EmsComputeConfig emsComputeConfig, Double result) {
        Long time = System.currentTimeMillis();
        ProcessedCalData processedCalData = ProcessedCalData.builder()
                .pid(emsComputeConfig.getComputeCode()).dqf(19).type(emsComputeConfig.getMeasureType())
                .unit(emsComputeConfig.getUnit())
                .remark(emsComputeConfig.getComputeName())
                .deviceCode(emsComputeConfig.getDeviceCode())
                .dataTime(time).processedTime(time).time(time)
                .val(result.toString())
                .build();
//        if(jepResult instanceof Number){
//            processedCalData.setVal(jepResult+"");
//        }else {
//            processedCalData.setVal(JSONObject.toJSON(jepResult).toString());
//        }
        return processedCalData;
    }

    /**
     * 遥测数据分批插入clickhouse (因为SQL有最大长度限制)
     * @param processedCalDataList 遥测数据
     * @param time 时间戳
     */
    public void insertAnalogData2ClickHouse(List<ProcessedCalData> processedCalDataList, Long time) {
        List<AnalogData> valueList = processedCalDataList.stream().map(p -> {
            return AnalogData.builder().v(p.getVal()).pid(p.getPid()).t(time).id(String.format("%s.%s", p.getPid(), time)).build();
        }).collect(Collectors.toList());
        String tableSuffix = TimeUtil.format2String(time, DateTimeFormatter.ofPattern("yyyyMMdd"));
        if (CollectionUtils.isEmpty(valueList)) {
            return;
        }
        long start = System.currentTimeMillis();
        for(int i = 0; i < valueList.size(); i = i + MAX_SAVE_COUNTS){
            int j = i + MAX_SAVE_COUNTS;
            if(j >= valueList.size()){
                j = valueList.size();
            }
            List<AnalogData> subList = valueList.subList(i, j);
            analogDataDAO.insertBatch(tableSuffix, subList);
        }
        long end = System.currentTimeMillis();
        log.info("遥测数据插入ClickHouse, 表名:{}, 插入个数:{}, 插入数据耗时:{}", COLLECTION_NAME_ANALOG_PREFIX + "_" + tableSuffix, valueList.size(), (end - start));
    }
}
